/** @file
  The Implementations for Information Exchange.

  (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Utility.h"
#include "IpSecDebug.h"
#include "IpSecConfigImpl.h"

/**
  Generate Information Packet.

  The information Packet may contain one Delete Payload, or Notify Payload, which
  dependes on the Context's parameters.

  @param[in]  SaSession   Pointer to IKE SA Session or Child SA Session which is
                          related to the information Exchange.
  @param[in]  Context     The Data passed from the caller. If the Context is not NULL
                          it should contain the information for Notification Data.

  @retval     Pointer of IKE_PACKET generated.

**/
IKE_PACKET *
Ikev2InfoGenerator (
  IN UINT8                         *SaSession,
  IN VOID                          *Context
  )
{
  IKEV2_SA_SESSION            *IkeSaSession;
  IKEV2_CHILD_SA_SESSION      *ChildSaSession;
  IKE_PACKET                  *IkePacket;
  IKE_PAYLOAD                 *IkePayload;
  IKEV2_INFO_EXCHANGE_CONTEXT *InfoContext;

  InfoContext  = NULL;
  IkeSaSession = (IKEV2_SA_SESSION *) SaSession;
  IkePacket    = IkePacketAlloc ();
  if (IkePacket == NULL) {
    return NULL;
  }

  //
  // Fill IkePacket Header.
  //
  IkePacket->Header->ExchangeType    = IKEV2_EXCHANGE_TYPE_INFO;
  IkePacket->Header->Version         = (UINT8) (2 << 4);

  if (Context != NULL) {
    InfoContext = (IKEV2_INFO_EXCHANGE_CONTEXT *) Context;
  }

  //
  // For Liveness Check
  //
  if (InfoContext != NULL &&
      (InfoContext->InfoType == Ikev2InfoLiveCheck || InfoContext->InfoType == Ikev2InfoNotify)
    ) {
    IkePacket->Header->MessageId       = InfoContext->MessageId;
    IkePacket->Header->InitiatorCookie = IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = IkeSaSession->ResponderCookie;
    IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_NONE;
    IkePacket->Header->Flags           = IKE_HEADER_FLAGS_RESPOND;
    //
    // TODO: add Notify Payload for Notification Information.
    //
    return IkePacket;
  }

  //
  // For delete SAs
  //
  if (IkeSaSession->SessionCommon.IkeSessionType == IkeSessionTypeIkeSa) {

    IkePacket->Header->InitiatorCookie = IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = IkeSaSession->ResponderCookie;

    //
    // If the information message is response message,the MessageId should
    // be same as the request MessageId which passed through the Context.
    //
    if (InfoContext != NULL) {
      IkePacket->Header->MessageId     = InfoContext->MessageId;
    } else {
      IkePacket->Header->MessageId     = IkeSaSession->MessageId;
      Ikev2SaSessionIncreaseMessageId (IkeSaSession);
    }
    //
    // If the state is on deleting generate a Delete Payload for it.
    //
    if (IkeSaSession->SessionCommon.State == IkeStateSaDeleting ) {
      IkePayload = Ikev2GenerateDeletePayload (
                     IkeSaSession,
                     IKEV2_PAYLOAD_TYPE_NONE,
                     0,
                     0,
                     NULL
                     );
      if (IkePayload == NULL) {
        goto ERROR_EXIT;
      }
      //
      // Fill the next payload in IkePacket's Header.
      //
      IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_DELETE;
      IKE_PACKET_APPEND_PAYLOAD (IkePacket, IkePayload);
      IkePacket->Private           = IkeSaSession->SessionCommon.Private;
      IkePacket->Spi               = 0;
      IkePacket->IsDeleteInfo      = TRUE;

    } else if (Context != NULL) {
      //
      // TODO: If contest is not NULL Generate a Notify Payload.
      //
    } else {
      //
      // The input parameter is not correct.
      //
      goto ERROR_EXIT;
    }

    if (IkeSaSession->SessionCommon.IsInitiator) {
      IkePacket->Header->Flags = IKE_HEADER_FLAGS_INIT ;
    }
  } else {
    //
    // Delete the Child SA Information Exchagne
    //
    ChildSaSession                     = (IKEV2_CHILD_SA_SESSION *) SaSession;
    IkeSaSession                       = ChildSaSession->IkeSaSession;
    IkePacket->Header->InitiatorCookie = ChildSaSession->IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = ChildSaSession->IkeSaSession->ResponderCookie;

    //
    // If the information message is response message,the MessageId should
    // be same as the request MessageId which passed through the Context.
    //
    if (InfoContext != NULL && InfoContext->MessageId != 0) {
      IkePacket->Header->MessageId     = InfoContext->MessageId;
    } else {
      IkePacket->Header->MessageId     = ChildSaSession->IkeSaSession->MessageId;
      Ikev2SaSessionIncreaseMessageId (IkeSaSession);
    }

    IkePayload     = Ikev2GenerateDeletePayload (
                       ChildSaSession->IkeSaSession,
                       IKEV2_PAYLOAD_TYPE_DELETE,
                       4,
                       1,
                       (UINT8 *)&ChildSaSession->LocalPeerSpi
                       );
    if (IkePayload == NULL) {
      goto ERROR_EXIT;
    }
    //
    // Fill the Next Payload in IkePacket's Header.
    //
    IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_DELETE;
    IKE_PACKET_APPEND_PAYLOAD (IkePacket, IkePayload);

    IkePacket->Private      = IkeSaSession->SessionCommon.Private;
    IkePacket->Spi          = ChildSaSession->LocalPeerSpi;
    IkePacket->IsDeleteInfo = TRUE;

    if (!ChildSaSession->SessionCommon.IsInitiator) {
      //
      // If responder, use the MessageId fromt the initiator.
      //
      IkePacket->Header->MessageId = ChildSaSession->MessageId;
    }

    //
    // Change the IsOnDeleting Flag
    //
    ChildSaSession->SessionCommon.IsOnDeleting = TRUE;

    if (ChildSaSession->SessionCommon.IsInitiator) {
      IkePacket->Header->Flags = IKE_HEADER_FLAGS_INIT ;
    }
  }

  if (InfoContext != NULL) {
    IkePacket->Header->Flags |= IKE_HEADER_FLAGS_RESPOND;
  }

  return IkePacket;

ERROR_EXIT:
   if (IkePacket != NULL) {
     FreePool (IkePacket);
   }
   return NULL;

}

/**
  Parse the Info Exchange.

  @param[in]  SaSession   Pointer to IKEV2_SA_SESSION.
  @param[in]  IkePacket   Pointer to IkePacket related to the Information Exchange.

  @retval  EFI_SUCCESS    The operation finised successed.

**/
EFI_STATUS
Ikev2InfoParser (
  IN UINT8                         *SaSession,
  IN IKE_PACKET                    *IkePacket
  )
{
  IKEV2_CHILD_SA_SESSION *ChildSaSession;
  IKEV2_SA_SESSION       *IkeSaSession;
  IKE_PAYLOAD            *DeletePayload;
  IKE_PAYLOAD            *IkePayload;
  IKEV2_DELETE           *Delete;
  LIST_ENTRY             *Entry;
  LIST_ENTRY             *ListEntry;
  UINT8                  Index;
  UINT32                 Spi;
  UINT8                  *SpiBuffer;
  IPSEC_PRIVATE_DATA     *Private;
  UINT8                  Value;
  EFI_STATUS             Status;
  IKE_PACKET             *RespondPacket;

  IKEV2_INFO_EXCHANGE_CONTEXT Context;

  IkeSaSession   = (IKEV2_SA_SESSION *) SaSession;

  DeletePayload  = NULL;
  Private        = NULL;
  RespondPacket  = NULL;
  Status         = EFI_SUCCESS;

  //
  // For Liveness Check
  //
  if (IkePacket->Header->NextPayload == IKEV2_PAYLOAD_TYPE_NONE &&
      (IkePacket->PayloadTotalSize == 0)
      ) {
    if (IkePacket->Header->Flags == IKE_HEADER_FLAGS_INIT) {
      //
      // If it is Liveness check request, reply it.
      //
      Context.InfoType  = Ikev2InfoLiveCheck;
      Context.MessageId = IkePacket->Header->MessageId;
      RespondPacket     = Ikev2InfoGenerator ((UINT8 *)IkeSaSession, &Context);

      if (RespondPacket == NULL) {
        Status = EFI_INVALID_PARAMETER;
        return Status;
      }
      Status = Ikev2SendIkePacket (
                 IkeSaSession->SessionCommon.UdpService,
                 (UINT8 *)(&IkeSaSession->SessionCommon),
                 RespondPacket,
                 0
                 );

    } else {
      //
      // Todo: verify the liveness check response packet.
      //
    }
    return Status;
  }

  //
  // For SA Delete
  //
  NET_LIST_FOR_EACH (Entry, &(IkePacket)->PayloadList) {

  //
  // Iterate payloads to find the Delete/Notify Payload.
  //
    IkePayload  = IKE_PAYLOAD_BY_PACKET (Entry);

    if (IkePayload->PayloadType == IKEV2_PAYLOAD_TYPE_DELETE) {
      DeletePayload = IkePayload;
      Delete = (IKEV2_DELETE *)DeletePayload->PayloadBuf;

      if (Delete->SpiSize == 0) {
        //
        // Delete IKE SA.
        //
        if (IkeSaSession->SessionCommon.State == IkeStateSaDeleting) {
          RemoveEntryList (&IkeSaSession->BySessionTable);
          Ikev2SaSessionFree (IkeSaSession);
          //
          // Checking the Private status.
          //
          //
          // when all IKE SAs were disabled by calling "IPsecConfig -disable", the IPsec
          // status should be changed.
          //
          Private = IkeSaSession->SessionCommon.Private;
          if (Private != NULL && Private->IsIPsecDisabling) {
            //
            // After all IKE SAs were deleted, set the IPSEC_STATUS_DISABLED value in
            // IPsec status variable.
            //
            if (IsListEmpty (&Private->Ikev1EstablishedList) &&
                (IsListEmpty (&Private->Ikev2EstablishedList))
               ) {
              Value  = IPSEC_STATUS_DISABLED;
              Status = gRT->SetVariable (
                         IPSECCONFIG_STATUS_NAME,
                         &gEfiIpSecConfigProtocolGuid,
                         EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                         sizeof (Value),
                         &Value
                         );
              if (!EFI_ERROR (Status)) {
                //
                // Set the DisabledFlag in Private data.
                //
                Private->IpSec.DisabledFlag = TRUE;
                Private->IsIPsecDisabling   = FALSE;
              }
            }
          }
        } else {
          IkeSaSession->SessionCommon.State = IkeStateSaDeleting;
          Context.InfoType                  = Ikev2InfoDelete;
          Context.MessageId                 = IkePacket->Header->MessageId;

          RespondPacket = Ikev2InfoGenerator ((UINT8 *)IkeSaSession, &Context);
          if (RespondPacket == NULL) {
            Status = EFI_INVALID_PARAMETER;
            return Status;
          }
          Status = Ikev2SendIkePacket (
                     IkeSaSession->SessionCommon.UdpService,
                     (UINT8 *)(&IkeSaSession->SessionCommon),
                     RespondPacket,
                     0
                     );
        }
      } else if (Delete->SpiSize == 4) {
        //
        // Move the Child SAs to DeleteList
        //
        SpiBuffer = (UINT8 *)(Delete + 1);
        for (Index = 0; Index < Delete->NumSpis; Index++) {
          Spi = ReadUnaligned32 ((UINT32 *)SpiBuffer);
          for (ListEntry = IkeSaSession->ChildSaEstablishSessionList.ForwardLink;
               ListEntry != &IkeSaSession->ChildSaEstablishSessionList;
          ) {
            ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (ListEntry);
            ListEntry = ListEntry->ForwardLink;

            if (ChildSaSession->RemotePeerSpi == HTONL(Spi)) {
              if (ChildSaSession->SessionCommon.State != IkeStateSaDeleting) {

                //
                // Insert the ChildSa Session into Delete List.
                //
                InsertTailList (&IkeSaSession->DeleteSaList, &ChildSaSession->ByDelete);
                ChildSaSession->SessionCommon.State       = IkeStateSaDeleting;
                ChildSaSession->SessionCommon.IsInitiator = FALSE;
                ChildSaSession->MessageId                 = IkePacket->Header->MessageId;

                Context.InfoType = Ikev2InfoDelete;
                Context.MessageId = IkePacket->Header->MessageId;

                RespondPacket = Ikev2InfoGenerator ((UINT8 *)ChildSaSession, &Context);
                if (RespondPacket == NULL) {
                  Status = EFI_INVALID_PARAMETER;
                  return Status;
                }
                Status = Ikev2SendIkePacket (
                           ChildSaSession->SessionCommon.UdpService,
                           (UINT8 *)(&ChildSaSession->SessionCommon),
                           RespondPacket,
                           0
                           );
              } else {
                //
                // Delete the Child SA.
                //
                Ikev2ChildSaSilentDelete (IkeSaSession, Spi);
                RemoveEntryList (&ChildSaSession->ByDelete);
              }
            }
          }
          SpiBuffer = SpiBuffer + sizeof (Spi);
        }
      }
    }
  }

  return Status;
}

GLOBAL_REMOVE_IF_UNREFERENCED IKEV2_PACKET_HANDLER  mIkev2Info = {
  Ikev2InfoParser,
  Ikev2InfoGenerator
};
